{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8cc39cad",
   "metadata": {},
   "source": [
    "## 요약(Summary) Evaluators\n",
    "\n",
    "일부 메트릭은 실험의 개별 실행이 아닌 전체 실험 수준에서만 정의할 수 있습니다. \n",
    "\n",
    "예를 들어, 데이터 세트에서 시작된 실험의 **모든 실행에 걸쳐 분류자의 평가 점수를 계산** 하고 싶을 수 있습니다. \n",
    "\n",
    "이를 `summary_evaluators` 라고 합니다. \n",
    "\n",
    "이러한 평가자는 하나의 Run과 Example 대신 각각의 목록을 가져옵니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "34a30a84",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 설치\n",
    "# !pip install -qU langsmith langchain-teddynote"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d75d1492",
   "metadata": {},
   "outputs": [],
   "source": [
    "# API KEY를 환경변수로 관리하기 위한 설정 파일\n",
    "from dotenv import load_dotenv\n",
    "\n",
    "# API KEY 정보로드\n",
    "load_dotenv()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "633d9db2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# LangSmith 추적을 설정합니다. https://smith.langchain.com\n",
    "# !pip install -qU langchain-teddynote\n",
    "from langchain_teddynote import logging\n",
    "\n",
    "# 프로젝트 이름을 입력합니다.\n",
    "logging.langsmith(\"CH16-Evaluations\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a09c8411",
   "metadata": {},
   "source": [
    "## RAG 성능 테스트를 위한 함수 정의"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "011f17eb",
   "metadata": {},
   "source": [
    "테스트에 활용할 RAG 시스템을 생성하겠습니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9bb9be51",
   "metadata": {},
   "outputs": [],
   "source": [
    "from myrag import PDFRAG\n",
    "\n",
    "\n",
    "# 질문에 대한 답변하는 함수를 생성\n",
    "def ask_question_with_llm(llm):\n",
    "    # PDFRAG 객체 생성\n",
    "    rag = PDFRAG(\n",
    "        \"data/SPRI_AI_Brief_2023년12월호_F.pdf\",\n",
    "        llm,\n",
    "    )\n",
    "\n",
    "    # 검색기(retriever) 생성\n",
    "    retriever = rag.create_retriever()\n",
    "\n",
    "    # 체인(chain) 생성\n",
    "    rag_chain = rag.create_chain(retriever)\n",
    "\n",
    "    def _ask_question(inputs: dict):\n",
    "        # 질문에 대한 컨텍스트 검색\n",
    "        context = retriever.invoke(inputs[\"question\"])\n",
    "        # 검색된 문서들을 하나의 문자열로 결합\n",
    "        context = \"\\n\".join([doc.page_content for doc in context])\n",
    "        # 질문, 컨텍스트, 답변을 포함한 딕셔너리 반환\n",
    "        return {\n",
    "            \"question\": inputs[\"question\"],\n",
    "            \"context\": context,\n",
    "            \"answer\": rag_chain.invoke(inputs[\"question\"]),\n",
    "        }\n",
    "\n",
    "    return _ask_question"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb22ba59",
   "metadata": {},
   "source": [
    "GPT-4o-mini 모델과 Ollama 모델을 활용하여 질문에 대한 답변을 생성하는 함수를 생성합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c95e0cd6",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_openai import ChatOpenAI\n",
    "from langchain_ollama import ChatOllama\n",
    "\n",
    "gpt_chain = ask_question_with_llm(ChatOpenAI(model=\"gpt-4o-mini\", temperature=0))\n",
    "ollama_chain = ask_question_with_llm(ChatOllama(model=\"EEVE-Korean-10.8B:latest\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eec5d15c",
   "metadata": {},
   "source": [
    "`OpenAIRelevanceGrader` 는 질문(Question), 컨텍스트(Context), 답변(Answer) 가 관련성이 있는지 여부를 평가하는 데 사용됩니다.\n",
    "\n",
    "- `target=\"retrieval-question\"`: 질문과 컨텍스트가 관련성이 있는지 여부를 평가합니다.\n",
    "- `target=\"retrieval-answer\"`: 답변과 컨텍스트가 관련성이 있는지 여부를 평가합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4b2f4dbd",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_teddynote.evaluator import OpenAIRelevanceGrader\n",
    "from langchain_openai import ChatOpenAI\n",
    "\n",
    "\n",
    "rq_grader = OpenAIRelevanceGrader(\n",
    "    llm=ChatOpenAI(model=\"gpt-4o-mini\", temperature=0), target=\"retrieval-question\"\n",
    ").create()\n",
    "\n",
    "ra_grader = OpenAIRelevanceGrader(\n",
    "    llm=ChatOpenAI(model=\"gpt-4o-mini\", temperature=0), target=\"retrieval-answer\"\n",
    ").create()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d6f39f18",
   "metadata": {},
   "outputs": [],
   "source": [
    "rq_grader.invoke(\n",
    "    {\n",
    "        \"input\": \"삼성전자가 자체 개발한 생성형 AI 의 이름은?\",\n",
    "        \"context\": \"삼성전자 AI 는 빅스비에요\",\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c6b9945c",
   "metadata": {},
   "outputs": [],
   "source": [
    "ra_grader.invoke(\n",
    "    {\n",
    "        \"input\": \"삼성전자가 자체 개발한 생성형 AI 는 가우스 입니다.\",\n",
    "        \"context\": \"삼성전자 AI 는 빅스비에요\",\n",
    "    }\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "38963fe4",
   "metadata": {},
   "source": [
    "## 관련성(Relevance) 평가를 종합하는 Summary Evaluator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ac5af18",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import List\n",
    "from langsmith.schemas import Example, Run\n",
    "from langsmith.evaluation import evaluate\n",
    "\n",
    "\n",
    "def relevance_score_summary_evaluator(runs: List[Run], examples: List[Example]) -> dict:\n",
    "    rq_scores = 0  # 질문 관련성 점수\n",
    "    ra_scores = 0  # 답변 관련성 점수\n",
    "\n",
    "    for run, example in zip(runs, examples):\n",
    "        question = example.inputs[\"question\"]\n",
    "        context = run.outputs[\"context\"]\n",
    "        prediction = run.outputs[\"answer\"]\n",
    "\n",
    "        # 질문 관련성 평가\n",
    "        rq_score = rq_grader.invoke(\n",
    "            {\n",
    "                \"input\": question,\n",
    "                \"context\": context,\n",
    "            }\n",
    "        )\n",
    "        # 답변 관련성 평가\n",
    "        ra_score = ra_grader.invoke(\n",
    "            {\n",
    "                \"input\": prediction,\n",
    "                \"context\": context,\n",
    "            }\n",
    "        )\n",
    "\n",
    "        # 관련성 점수 누적\n",
    "        if rq_score.score == \"yes\":\n",
    "            rq_scores += 1\n",
    "        if ra_score.score == \"yes\":\n",
    "            ra_scores += 1\n",
    "\n",
    "    # 최종 관련성 점수 계산 (질문 관련성과 답변 관련성의 평균)\n",
    "    final_score = ((rq_scores / len(runs)) + (ra_scores / len(runs))) / 2\n",
    "\n",
    "    return {\"key\": \"relevance_score\", \"score\": final_score}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ae3347c",
   "metadata": {},
   "source": [
    "평가를 진행합니다."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "988d0f72",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 평가 실행\n",
    "dataset_name = \"RAG_EVAL_DATASET\"\n",
    "\n",
    "experiment_result1 = evaluate(\n",
    "    gpt_chain,\n",
    "    data=dataset_name,\n",
    "    summary_evaluators=[relevance_score_summary_evaluator],\n",
    "    experiment_prefix=\"SUMMARY_EVAL\",\n",
    "    # 실험 메타데이터 지정\n",
    "    metadata={\n",
    "        \"variant\": \"GPT-4o-mini 사용: summary_evaluator 를 활용한 relevance 평가\",\n",
    "    },\n",
    ")\n",
    "\n",
    "# 평가 실행\n",
    "experiment_result2 = evaluate(\n",
    "    ollama_chain,\n",
    "    data=dataset_name,\n",
    "    summary_evaluators=[relevance_score_summary_evaluator],\n",
    "    experiment_prefix=\"SUMMARY_EVAL\",\n",
    "    # 실험 메타데이터 지정\n",
    "    metadata={\n",
    "        \"variant\": \"Ollama(EEVE-Korean-10.8B:latest) 사용: summary_evaluator 를 활용한 relevance 평가\",\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f9e86c3",
   "metadata": {},
   "source": [
    "결과를 확인합니다. \n",
    "\n",
    "(참고) 개별 데이터셋에 대한 평가는 확인할 수 없으며, 실험(Experiment) 단위로 확인할 수 있습니다."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bcca1910",
   "metadata": {},
   "source": [
    "![](./assets/eval-08.png)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-kr-lwwSZlnu-py3.11",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
